package com.ib.algokit.marketscan;

import static org.activequant.util.tools.IdentityUtils.safeCompare;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.activequant.container.services.ITimeStampService;
import org.activequant.core.domainmodel.marketscanner.MarketScanChange;
import org.activequant.core.types.TimeStamp;
import org.activequant.util.pattern.events.IEventListener;
import org.apache.log4j.Logger;
/**
 * Holds the following associated variables:
 * <ul>
 * <li>timeStampService(ITimeStampService)</li>
 * <li>marketScanEventProcessingStrategy(IMarketScanEventProcessingStrategy)</li>
 * <li>recentVolatileAddedSwings(List&lt;MarketScanChange&gt;)</li>
 * <li>recentVolatileRemovedSwings(List&lt;MarketScanChange&gt;)</li>
 * <li>recentVolatileMovedUpSwings(List&lt;MarketScanChange&gt;)</li>
 * <li>recentVolatileMovedDownSwings(List&lt;MarketScanChange&gt;)</li>
 * <li></li>
 * <li></li>
 * <li></li>
 * <li></li>
 * </ul>
 * @author Dimitar
 *
 */
public class MarketScanSwingDetector {	
	protected final static Logger log = Logger.getLogger(MarketScanSwingDetector.class);
	/**
	 * private long HISTORY_SIZE=120000000000L;<br/>
	 * last 120 seconds in history
	 */
	private long HISTORY_SIZE=120000000000L; //last 120 seconds in history
	/**
	 * private ITimeStampService timeStampService;
	 */
	private ITimeStampService timeStampService;
	/**
	 * private IMarketScanEventProcessingStrategy marketScanEventProcessingStrategy;
	 */
	private IMarketScanEventProcessingStrategy marketScanEventProcessingStrategy;
	/**
	 * private List&lt;MarketScanChange&gt; recentVolatileAddedSwings = Collections.synchronizedList(new ArrayList&lt;MarketScanChange&gt;());
	 */
	private List<MarketScanChange> recentVolatileAddedSwings = Collections.synchronizedList(new ArrayList<MarketScanChange>());
	/**
	 * private List&lt;MarketScanChange&gt; recentVolatileRemovedSwings = Collections.synchronizedList(new ArrayList&lt;MarketScanChange&gt;());
	 */
	private List<MarketScanChange> recentVolatileRemovedSwings = Collections.synchronizedList(new ArrayList<MarketScanChange>());
	/**
	 * private List&lt;MarketScanChange&gt; recentVolatileMovedUpSwings = Collections.synchronizedList(new ArrayList&lt;MarketScanChange&gt;());
	 */
	private List<MarketScanChange> recentVolatileMovedUpSwings = Collections.synchronizedList(new ArrayList<MarketScanChange>());
	/**
	 * private List&lt;MarketScanChange&gt; recentVolatileMovedDownSwings = Collections.synchronizedList(new ArrayList&lt;MarketScanChange&gt;());
	 */
	private List<MarketScanChange> recentVolatileMovedDownSwings = Collections.synchronizedList(new ArrayList<MarketScanChange>());
	/**
	 * Retrieves/removes the most recent most volatile MarketScanChange from the associated recentVolatileAddedSwings(List&lt;MarketScanChange&gt;).<br/>
	 * If there aren't any, returns null. Good for fetch on demand
	 * @return
	 */
	public MarketScanChange getMostRecentVolatileAddedSwing(){
		synchronized(recentVolatileAddedSwings) {
			int size=recentVolatileAddedSwings.size();
			if(recentVolatileAddedSwings.size()!=0){
				return recentVolatileAddedSwings.remove(size-1);
			}else{
				return null;
			}			
		}	
	}
	/**
	 * Retrieves/removes the most recent most volatile MarketScanChange from the associated recentVolatileRemovedSwings(List&lt;MarketScanChange&gt;).
	 * If there aren't any, returns null. Good for fetch on demand
	 * @return
	 */
	public MarketScanChange getMostRecentVolatileRemovedSwing(){
		synchronized(recentVolatileRemovedSwings) {
			int size=recentVolatileRemovedSwings.size();
			if(recentVolatileRemovedSwings.size()!=0){
				return recentVolatileRemovedSwings.remove(size-1);
			}else{
				return null;
			}			
		}	
	}
	/**
	 * Retrieves/removes the most recent most volatile MarketScanChange from the associated recentVolatileMovedUpSwings(List&lt;MarketScanChange&gt;).
	 * If there aren't any, returns null. Good for fetch on demand
	 * @return
	 */
	public MarketScanChange getMostRecentVolatileMovedUpSwing(){
		synchronized(recentVolatileMovedUpSwings) {
			int size=recentVolatileMovedUpSwings.size();
			if(recentVolatileMovedUpSwings.size()!=0){
				return recentVolatileMovedUpSwings.remove(size-1);
			}else{
				return null;
			}			
		}	
	}
	/**
	 * Retrieves/removes the most recent most volatile MarketScanChange from the associated recentVolatileMovedDownSwings(List&lt;MarketScanChange&gt;).
	 * If there aren't any, returns null. Good for fetch on demand
	 * @return
	 */
	public MarketScanChange getMostRecentVolatileMovedDownSwing(){
		synchronized(recentVolatileMovedDownSwings) {
			int size=recentVolatileMovedDownSwings.size();
			if(recentVolatileMovedDownSwings.size()!=0){
				return recentVolatileMovedDownSwings.remove(size-1);
			}else{
				return null;
			}			
		}	
	}
	
	public void addRecentVolatileAddedSwing(MarketScanChange change){
		TimeStamp cutOffTime = new TimeStamp(timeStampService.currentTimeStamp().getNanoseconds()-HISTORY_SIZE);		
		synchronized(recentVolatileAddedSwings) {			
//			printAddedContents(recentVolatileAddedSwings);
//			log.info("about to add:"+change.getInstrumentSpecificationDetails().getInstrumentSpecification().getSymbol().toString()+" new:"+change.getNewScanTime().getDate()+" prev:"+change.getPreviousScanTime().getDate());
			Iterator<MarketScanChange> i = recentVolatileAddedSwings.iterator(); // Must be in synchronized block
			while (i.hasNext()){
				MarketScanChange event = i.next();
				if(event.getTimeStamp().isBefore(cutOffTime)){
					i.remove();
				}else{
					break;
				}
			}
			recentVolatileAddedSwings.add(change);
			Collections.sort(recentVolatileAddedSwings, new MarketScanChangeComparator());
			//printAddedContents(recentVolatileAddedSwings);
		}	
	}
	
	public void addRecentVolatileRemovedSwing(MarketScanChange change){
		TimeStamp cutOffTime = new TimeStamp(timeStampService.currentTimeStamp().getNanoseconds()-HISTORY_SIZE);		
		synchronized(recentVolatileRemovedSwings) {	
			//printRemovedContents(recentVolatileRemovedSwings);
			//log.info("about to add:"+change.getInstrumentSpecificationDetails().getInstrumentSpecification().getSymbol().toString());
			Iterator<MarketScanChange> i = recentVolatileRemovedSwings.iterator(); // Must be in synchronized block
			while (i.hasNext()){
				MarketScanChange event = i.next();
				if(event.getTimeStamp().isBefore(cutOffTime)){
					i.remove();
				}else{
					break;
				}
			}
			recentVolatileRemovedSwings.add(change);
			Collections.sort(recentVolatileRemovedSwings, new MarketScanChangeComparator());
			//printRemovedContents(recentVolatileRemovedSwings);
		}	
	}
	
	public void addRecentVolatileMovedUpSwing(MarketScanChange change){
		TimeStamp cutOffTime = new TimeStamp(timeStampService.currentTimeStamp().getNanoseconds()-HISTORY_SIZE);		
		synchronized(recentVolatileMovedUpSwings) {			
			Iterator<MarketScanChange> i = recentVolatileMovedUpSwings.iterator(); // Must be in synchronized block
			while (i.hasNext()){
				MarketScanChange event = i.next();
				if(event.getTimeStamp().isBefore(cutOffTime)){
					i.remove();
				}else{
					break;
				}
			}
			recentVolatileMovedUpSwings.add(change);
			Collections.sort(recentVolatileMovedUpSwings, new MarketScanChangeComparator());
		}	
	}
	
	public void addRecentVolatileMovedDownSwing(MarketScanChange change){
		TimeStamp cutOffTime = new TimeStamp(timeStampService.currentTimeStamp().getNanoseconds()-HISTORY_SIZE);		
		synchronized(recentVolatileMovedDownSwings) {			
			Iterator<MarketScanChange> i = recentVolatileMovedDownSwings.iterator(); // Must be in synchronized block
			while (i.hasNext()){
				MarketScanChange event = i.next();
				if(event.getTimeStamp().isBefore(cutOffTime)){
					i.remove();
				}else{
					break;
				}
			}
			recentVolatileMovedDownSwings.add(change);
			Collections.sort(recentVolatileMovedDownSwings, new MarketScanChangeComparator());
		}	
	}
	
	public MarketScanSwingDetector(IMarketScanEventProcessingStrategy marketScanEventProcessingStrategy, ITimeStampService timeStampService){
		this.marketScanEventProcessingStrategy=marketScanEventProcessingStrategy;
		this.timeStampService=timeStampService;
		this.marketScanEventProcessingStrategy.subscribeForInstrumentAddedEvents(instrumentAddedListener);
		this.marketScanEventProcessingStrategy.subscribeForInstrumentRemovedEvents(instrumentRemovedListener);
		this.marketScanEventProcessingStrategy.subscribeForInstrumentMovedUpEvents(instrumentMovedUplistener);
		this.marketScanEventProcessingStrategy.subscribeForInstrumentMovedDownEvents(instrumentMovedDownlistener);
	}
	
	public void unsubscribe(){
		this.marketScanEventProcessingStrategy.unsubscribeFromInstrumentAddedEvents(instrumentAddedListener);
		this.marketScanEventProcessingStrategy.unsubscribeFromInstrumentRemovedEvents(instrumentRemovedListener);
		this.marketScanEventProcessingStrategy.unsubscribeFromInstrumentMovedUpEvents(instrumentMovedUplistener);
		this.marketScanEventProcessingStrategy.unsubscribeFromInstrumentMovedDownEvents(instrumentMovedDownlistener);
	}
	
	private IEventListener<MarketScanChange> instrumentAddedListener=new IEventListener<MarketScanChange>(){
		public void eventFired(MarketScanChange marketScanInstrumentAddedChange) {	
			double magnitude=marketScanInstrumentAddedChange.getMagnitude();
			if(magnitude<0.2)return;
			addRecentVolatileAddedSwing(marketScanInstrumentAddedChange);	
		}			
	};
	
	private IEventListener<MarketScanChange> instrumentRemovedListener=new IEventListener<MarketScanChange>(){
		public void eventFired(MarketScanChange marketScanInstrumentRemovedChange) {
			double magnitude=marketScanInstrumentRemovedChange.getMagnitude();
			if(magnitude<0.2)return;		
			addRecentVolatileRemovedSwing(marketScanInstrumentRemovedChange);
		}			
	};
	
	private IEventListener<MarketScanChange> instrumentMovedUplistener=new IEventListener<MarketScanChange>(){
		public void eventFired(MarketScanChange marketScanInstrumentMovedUpChange) {
			double magnitude=marketScanInstrumentMovedUpChange.getMagnitude();
			if(magnitude<0.2)return;		
			addRecentVolatileMovedUpSwing(marketScanInstrumentMovedUpChange);
		}			
	};
	
	private IEventListener<MarketScanChange> instrumentMovedDownlistener=new IEventListener<MarketScanChange>(){
		public void eventFired(MarketScanChange marketScanInstrumentMovedDownChange) {
			double magnitude=marketScanInstrumentMovedDownChange.getMagnitude();
			if(magnitude<0.2)return;
			addRecentVolatileMovedDownSwing(marketScanInstrumentMovedDownChange);
		}			
	};

	public IEventListener<MarketScanChange> getInstrumentAddedListener() {
		return instrumentAddedListener;
	}

	public IEventListener<MarketScanChange> getInstrumentRemovedListener() {
		return instrumentRemovedListener;
	}

	public IEventListener<MarketScanChange> getInstrumentMovedUplistener() {
		return instrumentMovedUplistener;
	}

	public IEventListener<MarketScanChange> getInstrumentMovedDownlistener() {
		return instrumentMovedDownlistener;
	}

	public IMarketScanEventProcessingStrategy getMarketScanEventProcessingStrategy() {
		return marketScanEventProcessingStrategy;
	}

	public void setMarketScanEventProcessingStrategy(IMarketScanEventProcessingStrategy marketScanEventProcessingStrategy) {
		this.marketScanEventProcessingStrategy = marketScanEventProcessingStrategy;
	}
	
	public ITimeStampService getTimeStampService() {
		return timeStampService;
	}

	public void setTimeStampService(ITimeStampService timeStampService) {
		this.timeStampService = timeStampService;
	}

	class MarketScanChangeComparator implements Comparator<MarketScanChange> {
		
		public int compare(MarketScanChange o1, MarketScanChange o2) {
			int rc;
			long newTime1=o1.getTimeStamp().getNanoseconds()/20000000000l; //20 seconds
			long newTime2=o2.getTimeStamp().getNanoseconds()/20000000000l; //20 seconds
			rc = safeCompare(newTime1, newTime2);
			if (rc != 0) return rc;
			long prevTime1=o1.getPreviousScanTime().getNanoseconds()/20000000000l; //20 seconds
			long prevTime2=o2.getPreviousScanTime().getNanoseconds()/20000000000l; //20 seconds
			rc = safeCompare(prevTime1, prevTime2);
			if (rc != 0) return rc;
//			rc = safeCompare(o1.getPreviousScanTime(), o2.getPreviousScanTime());
//			if (rc != 0) return rc;
//			rc = safeCompare(o1.getChangeInterval(), o2.getChangeInterval());
//			if (rc != 0) return rc;
			rc = safeCompare(o1.getMagnitude(), o2.getMagnitude());
			if (rc != 0) return rc;
			return rc;			
		}
	}
	
	public void printAddedContents(List<MarketScanChange> changes){
		StringBuffer buffer=new StringBuffer();
		if(changes!=null){
			int size=changes.size();
			for(int i=0;i<size;i++){
				MarketScanChange change=changes.get(i);
				double magnitude= change.getMagnitude();
				String symbol=change.getInstrumentSpecificationDetails().getInstrumentSpecification().getSymbol().toString();
				buffer.append("("+symbol+" mag:"+magnitude+")");
				buffer.append(" ");
			}
		}
		log.info("Added in"+":"+buffer.toString());
	}
	
	public void printRemovedContents(List<MarketScanChange> changes){
		StringBuffer buffer=new StringBuffer();
		if(changes!=null){
			int size=changes.size();
			for(int i=0;i<size;i++){
				MarketScanChange change=changes.get(i);
				double magnitude= change.getMagnitude();
				String symbol=change.getInstrumentSpecificationDetails().getInstrumentSpecification().getSymbol().toString();
				buffer.append("("+symbol+" mag:"+magnitude+")");
				buffer.append(" ");
			}
		}
		log.info("Removed:"+buffer.toString());
	}
}
